﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SalesmanSolver.cs" company="spbsu">
//   Alexander Ulitin alexander.a.ulitin@gmail.com
// </copyright>
// <summary>
//   The my gaussian function.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace NeuronDotNet.Samples.TspSolver
{
    using System;
    using System.Runtime.Serialization;

    using NeuronDotNet.Core.SOM;

    /// <summary>
    /// The my gaussian function.
    /// </summary>
    public sealed class MyGaussianFunction : INeighborhoodFunction
    {
        private readonly double[] xValues;

        private readonly double[] yValues;

        /// <summary>
        /// The sigma.
        /// </summary>
        private readonly double sigma = 0d;

        public MyGaussianFunction(int learningRadius)
        {

        }

        public MyGaussianFunction(int learningRadius, double[] xValues, double[] yValues)
        {
            if (yValues == null) throw new ArgumentNullException();
            if (xValues == null) throw new ArgumentNullException();

            this.xValues = xValues;
            this.yValues = yValues;
            // Full Width at Half Maximum for a Gaussian curve 
            // = sigma * Math.Sqrt(2 * ln(2)) = sigma * 2.35482

            // Full Width at Half Maximum (FWHM) is nothing but learning diameter
            // so, learning radius = 1.17741 * sigma
            this.sigma = learningRadius / 1.17741d;
        }

        public void EvaluateNeighborhood(KohonenLayer layer, int currentIteration, int trainingEpochs)
        {
            // Winner co-ordinates
            int winnerX = layer.Winner.Coordinate.X;
            int winnerY = layer.Winner.Coordinate.Y;

            // Layer width and height
            int layerWidth = layer.Size.Width;
            int layerHeight = layer.Size.Height;

            // Sigma value uniformly decreases to zero as training progresses
            double currentSigma = this.sigma - ((this.sigma * currentIteration) / trainingEpochs);

            // Optimization measure: Pre-calculated 2-Sigma-Square
            double twoSigmaSquare = 2 * currentSigma * currentSigma;

            // Evaluate and update neighborhood value of each neuron
            foreach (PositionNeuron neuron in layer.Neurons)
            {
                // find highways
                //var val = neuron

                int dx = Math.Abs(winnerX - neuron.Coordinate.X);
                int dy = Math.Abs(winnerY - neuron.Coordinate.Y);

                if (layer.IsRowCircular)
                {
                    dx = Math.Min(dx, layerWidth - dx);
                }

                if (layer.IsColumnCircular)
                {
                    dy = Math.Min(dy, layerHeight - dy);
                }

                double k = 2;
                double dxSquare = dx * dx * k;
                double dySquare = dy * dy * k;
                if (layer.Topology == LatticeTopology.Hexagonal)
                {
                    if (dy % 2 == 1)
                    {
                        dxSquare += 0.25 + (((neuron.Coordinate.X > winnerX) == (winnerY % 2 == 0)) ? dx : -dx);
                    }

                    dySquare *= 0.75;
                }

                neuron.neighborhoodValue = Math.Exp(-(dxSquare + dySquare + 1) / twoSigmaSquare);
            }
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("sigma", this.sigma);
        }
    }
}